package hasthi.tools;

import hasthi.actuators.impl.HostAgentBasedHostProxy;
import hasthi.common.BaseContext;
import hasthi.common.ComponetFactory;
import hasthi.common.HasthiException;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;
import hasthi.common.constants.WSDMConstants;
import hasthi.container.CommunicationContianier;
import hasthi.container.MessageContext;
import hasthi.container.MessageProcessor;
import hasthi.container.SimpleMessageContext;
import hasthi.container.UnicastContianier;
import hasthi.manager.ExecuteOperationDocument;
import hasthi.manager.ManagementLogMessageDocument;
import hasthi.manager.ManagementLogMessageDocument.ManagementLogMessage;
import hasthi.manager.gui.UIContext;
import hasthi.manager.monitering.EventListener;
import hasthi.metamodel.ExternalResourceModel;
import hasthi.metamodel.resource.ManagedResource;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.URI;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.LogManager;
import org.apache.xmlbeans.XmlObject;

public class ElectionTester implements EventListener,MessageProcessor {
    private static DateFormat df = DateFormat.getTimeInstance();
    private static Log log = LogFactory.getLog(HasthiConstants.LOGGER_NAME);

    private static enum MngSysState {
        Starting, Healthy, HasCoordinator, UndetectedCoordinatorFailure, Electing
    };

    private static enum MngSysEvent {
        CoorEHealthy, CoorENotHealthy, ElectionStarted, ElectionDone
    };

    private MngSysState state = MngSysState.Starting;
    private Semaphore healthyCheck = new Semaphore(0, true);
    int managerCount = 10;
    int resourceCount = 100;
    private String coordinatorAddress;
    private UnicastContianier contianier;

    private long coordinatorFailedTs = -1;
    private long electionStartedTs = -1;
    private long electionDoneTs = -1;
    private long systemHalthyTs = -1;
    private BaseContext baseContext;

    private static Pattern coordhb = Pattern
            .compile("\\(([0-9]+)\\)Coordinator ([0-9]+) R->([0-9]+) Mgrs .*");
    private static Pattern electionDone = Pattern
            .compile("\\(([0-9]+)\\)Election Completed:(.*?) become the coordinator");
    private static Pattern electionStarted = Pattern
            .compile("\\(([0-9]+)\\)Election Started at (.*?)$");
    
    private PrintWriter out;
    private int electionCount = 20;
    private int iteration = 0; 

    public ElectionTester(BaseContext baseContext,int managerCount,int resourceCount,int electionCount) throws HasthiException {
        try {
            this.baseContext = baseContext;
//            MoniteringAgent moniteringAgent = new MoniteringAgent(baseContext.getMessageBrokerUrl(),
//                    baseContext.getManagementTopic());
//            moniteringAgent.addTrigger(this);
//            moniteringAgent.startMonitering();
            contianier = ComponetFactory.createSoapContianier(baseContext, this,12346,false);
            contianier.startContainer();
            out = new PrintWriter(new BufferedWriter(new FileWriter("election_summery.log"),512));
            this.resourceCount = resourceCount;
            this.managerCount = managerCount;
            this.electionCount = electionCount;
        } catch (IOException e) {
            throw new HasthiException(e,FaultCode.LocalError);
        }
    }

    public void doTest() throws Exception {
        try{
            UIContext context = null;
            Map<String, String> hostName2MngAgnetMap = new HashMap<String, String>();
            context = new UIContext(true);
            //context.se
            ExternalResourceModel externalResourceModel = context.getExternalResourceModel();
            ManagedResource[] resources = externalResourceModel.getAllResources();
            for (ManagedResource resource : resources) {
                if (resource.getName().endsWith("indiana.edu")) {
                    hostName2MngAgnetMap.put(resource.getQName().getLocalPart(), resource
                            .getManagerAgentURL());
                }
            }
            healthyCheck.acquire();
            for (int i = 0; i < electionCount; i++) {
                try {
                    iteration++;
                    // ManagedResource[] resources =
                    // externalResourceModel.getAllResources();
                    String ipAddress = new URI(coordinatorAddress).getHost();
                    InetAddress address = InetAddress.getByName(ipAddress);
                    String hostName = address.getCanonicalHostName();
                    String managerAgentURL = hostName2MngAgnetMap.get(hostName);
                    HostAgentBasedHostProxy remoteSystemProxy = new HostAgentBasedHostProxy(
                            baseContext, managerAgentURL, contianier);
                    remoteSystemProxy.runCommand(address.getCanonicalHostName(),
                            "/u/hperera/svncode/management/target/dist-bin/mngenode.sh", null,
                            "/u/hperera/svncode/management/target/dist-bin", false, "Manager Started And Joined Coordination Ring Sucessfully");
                    Thread.sleep(10000);
                    // Create new manager
                    shutDownCoordinator();
                    healthyCheck.acquire();
                    log.info("(" + i + ")##################Detected = "
                            + (electionStartedTs - coordinatorFailedTs) + " Election time = "
                            + (electionDoneTs - electionStartedTs) + " Recovery "
                            + (systemHalthyTs - electionDoneTs) + " end2end ="
                            + (systemHalthyTs - coordinatorFailedTs));
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    e.printStackTrace(out);
                    log.error(e);
                    System.exit(0);
                }
            }
            log.info("######################## TestCompleted ######################");
        }finally{
            LogManager.shutdown();
            out.flush();
            out.close();
            contianier.stopContainer();
            //Thread.sleep(10000000);
            System.exit(0);
        }
        
        // UIContext context = new UIContext(true);
        // // //
        // // ManagementRing ring =
        // ResourceModelFactory.getManagerRingState(context);
        // // int managerCount = ring.getManagerDataArray().length;
        // int resourceCount =
        // context.getExternalResourceModel().getAllResources().length;
    }

    private void shutDownCoordinator() throws HasthiException {
        ExecuteOperationDocument executeOperationDocument = ExecuteOperationDocument.Factory
                .newInstance();
        executeOperationDocument.addNewExecuteOperation().addNewOperation().setOperation(
                new QName(HasthiConstants.SHUTDOWN_OPERATION_NAME));
        MessageContext message = new SimpleMessageContext(executeOperationDocument,
                coordinatorAddress, WSDMConstants.ACTION_SHUTDOWN);
        contianier.sendReceive(message);
        log.info("(" + System.currentTimeMillis() + ")Invoked Shutdown operation on "+coordinatorAddress);
        state = MngSysState.UndetectedCoordinatorFailure;
        coordinatorFailedTs = System.currentTimeMillis();
    }

    public void addContainer(CommunicationContianier container) throws HasthiException {
        // TODO Auto-generated method stub
        
    }

    public XmlObject processMessage(MessageContext messageContext) throws HasthiException {
        //System.out.print(XmlBeanUtils.prettyPrint(messageContext.getRequest()));
        performTriggerAction(messageContext.getRequest(), null);
        return null;
    }

    public void setState(MngSysEvent event, long timestamp) {
        out.write("ET:"+iteration+","+electionCount+":" + df.format(new Date(timestamp)) +":" +state + "-(" + event +")->\n");
        log.info("ET:"+iteration+","+electionCount+":" + df.format(new Date(timestamp)) +":" +state + "-(" + event +")->");
        if (event.equals(MngSysEvent.CoorEHealthy)) {
            switch (state) { 
            case Starting:
            case HasCoordinator:
                state = MngSysState.Healthy;
                systemHalthyTs = timestamp;
                healthyCheck.release();
                break;
            case Healthy:// noop
                break;
            case UndetectedCoordinatorFailure:
            case Electing:
                if (Math.abs(coordinatorFailedTs - timestamp) > 1000) {
                    log.warn("ET:!!!!!!Received healthy while at " + state);
                }
            }
        } else if (event.equals(MngSysEvent.CoorENotHealthy)) {
            switch (state) {
            case Starting:
            case Healthy:
                state = MngSysState.HasCoordinator;
                break;
            case UndetectedCoordinatorFailure:
            case Electing:
                log.warn("ET:!!! Received Has Coordinator while at " + state);
            }
        }else if(event.equals(MngSysEvent.ElectionStarted)) {
            electionCount++;
            switch (state) {
            case Starting:
            case HasCoordinator:
            case Healthy:
                log.warn("ET:!!! Received Election start while at " + state);
                break;
            case UndetectedCoordinatorFailure:
                state = MngSysState.Electing;
                electionStartedTs = timestamp;
            }
        }else if(event.equals(MngSysEvent.ElectionDone)) { 
            electionCount--;
            switch (state) {
            case Starting:
            case HasCoordinator:
            case Healthy:
            case UndetectedCoordinatorFailure:
                log.warn("ET:!!!!!Received Election Done while at " + state + " without starting election");
                state = MngSysState.HasCoordinator;
                break;
            case Electing:
                state = MngSysState.HasCoordinator;
                electionDoneTs = timestamp;
                log.warn(state + ":" + df.format(new Date(timestamp)));
            }
        }
    }

    public String getSearchPath() {
        // TODO Auto-generated method stub
        return null;
    }

    public void performTriggerAction(XmlObject event, XmlObject[] matchingResults)
            throws HasthiException {
        if (event instanceof ManagementLogMessageDocument) {
            ManagementLogMessage logMessage = ((ManagementLogMessageDocument) event)
                    .getManagementLogMessage();
            String messsage = logMessage.getMesssage();
            //
            Matcher m = coordhb.matcher(messsage);
            if (m.matches()) {
                log.info(messsage);
                coordinatorAddress = logMessage.getSender();
                long timestamp = Long.parseLong(m.group(1));
                int rcount = Integer.parseInt(m.group(2));
                int mcount = Integer.parseInt(m.group(3));
                if (mcount >= managerCount && rcount >= resourceCount * 0.9) {
                        //|| System.currentTimeMillis() - electionDoneTs > 5*60*1000) {
                    setState(MngSysEvent.CoorEHealthy, timestamp);
                } else {
                    setState(MngSysEvent.CoorENotHealthy, timestamp);
                }
                return;
            }

            m = electionStarted.matcher(messsage);
            if (m.matches()) {
                long timestamp = Long.parseLong(m.group(1));
                setState(MngSysEvent.ElectionStarted, timestamp);
                return;
            }
            m = electionDone.matcher(messsage);
            if (m.matches()) {
                long timestamp = Long.parseLong(m.group(1));
                setState(MngSysEvent.ElectionDone, timestamp);
                return;
            }

            // "Coordinator "+context.getResourceCount() + " R->"+
            // context.getManagerCount()+" Mgrs "+
            // new Date() + " ("+JavaInstrumentation.getMemoryUsage()+")";
            // managerContext.getMyAddress().getSoapAddress() + " become the
            // coordinator"
            // dlogger.info("Election Phase2 Started at
            // "+managerContext.getMyAddress().getSoapAddress());
        }

    }

    public static void main(String[] args) throws Exception {
        int managerCount;
        int resourceCount;
        int electionCount;
        ElectionTester tester;
        if(args.length == 3){
            managerCount = Integer.parseInt(args[0]);
            resourceCount = Integer.parseInt(args[1]);
            electionCount = Integer.parseInt(args[2]);
        }else{
            managerCount =  100;
            resourceCount = 10000;
            electionCount = 100;
//            managerCount =  20;
//            resourceCount = 100;
//            electionCount = 10;
        }
        tester = new ElectionTester(new BaseContext(),managerCount,resourceCount,electionCount);
        
        //Comment this out for real test
//        Thread.sleep(5000);
//        SingleJvmManagmentNodeSet nodeSet = new SingleJvmManagmentNodeSet();
//        // nodeSet.loadFromArgs(new String[]{"-B","-X","2"});
//        nodeSet.loadFromArgs(new String[] { "-B", String.valueOf(managerCount) });
//        nodeSet.startNodeSet();
//        DummyServiceSet.main(new String[] { String.valueOf(resourceCount) });
        //////////////////
        tester.doTest();
    }
}
